
import os, sys, tempfile, signal, time, json, _thread
from subprocess import Popen, PIPE, CalledProcessError
from threading import Timer

def decode_text(text, info):
	if text is None: return None
	codes = ['utf-8', 'windows-1252', 'ISO-8859-1', 'GB2312']
	for code in codes:
		try:
			return text.decode(code)
		except:
			pass
	assert False, '%s Input file cannot be decoded, please try to use a different browser.' % info

def run_cmd(cmd, files, temp_dir, info, timeout=1, shell=True):
	# print('RUN', cmd)
	for name in files:
		if files[name] is not None:
			with open(os.path.join(temp_dir, name), 'w') as f:
				f.write(files[name])
	try:
		proc = Popen(cmd, cwd=temp_dir, stdout=PIPE, stderr=PIPE, shell=shell, preexec_fn=os.setsid)
		is_timeout = False
		def kill_proc(proc):
			nonlocal is_timeout
			os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
			is_timeout = True
		timer = Timer(timeout, kill_proc, (proc,))
		try:
			timer.start()
			output, error = proc.communicate() # stdout, stderr
			output = decode_text(output, 'The stdout output of ' + info)
			error = decode_text(error, 'The stderr output of ' + info)
			if is_timeout: 
				return f'{info} 超时 ({timeout}秒)', '', ''
			return '', output, error
		finally:
			timer.cancel()
			if is_timeout: 
				return f'{info} 超时 ({timeout} 秒)', '', ''
	except CalledProcessError:
		assert False, 'The procedure returns nonzero.'

def find_included(source):
	included = set()
	for line in source.split('\n'):
		words = [w.strip() for w in line.split()]
		if len(words) >= 2 and words[0] == '#include':
			included.add(words[1])
	return included

def get_cp_cmd(app, cpp_cwd):
	is_cpp = False
	is_c = False
	if app.endswith('.cpp'):
		is_cpp = True
	if app.endswith('.c'):
		is_c = True
	assert is_cpp or is_c, '项目必须包含 ".cpp" 或 ".c" 源文件'
	exec_out = os.path.join(cpp_cwd, app.split('.')[0])
	if is_cpp:
		return f'g++ -std=c++11 -O1 -fno-omit-frame-pointer -fsanitize=address -fPIE {app} -o {exec_out}'
	else:
		return f'gcc -fno-omit-frame-pointer -fsanitize=address -fPIE {app} -o {exec_out}'

_tmp = '_tmp'
_lock = _thread.allocate_lock()
def _mtime(p):
	mt = 0
	for f in os.listdir(p):
		f = os.path.join(p,f)
		t = os.path.getmtime(f)
		mt = max(mt,t)
	return mt
def _cleaner_thread():
	while True:
		time.sleep(1)
		t = time.time()
		with _lock:
			for p in os.listdir(_tmp):
				p = os.path.join(_tmp,p)
				if os.path.isdir(p):
					mt = _mtime(p)
					if t-mt>10*60: # 10分钟
						_remove_folder(p)
def _remove_folder(p):
	if os.path.isdir(p):
		try:
			for f in os.listdir(p):
				os.unlink(os.path.join(p,f))
			os.rmdir(p)
		except:
			import traceback
			traceback.print_exc()
			return False
	return True
_thread.start_new_thread(_cleaner_thread, ())

def run_source(app, data):
	cpp_cwd = f'{_tmp}/{data["session"]}'
	try:
		assert sys.platform in ('darwin', 'cygwin', 'linux'), f'不支持的操作系统: {sys.platform}'
		if not os.path.isdir(_tmp):
			os.mkdir(_tmp)
		if not os.path.isdir(cpp_cwd):
			os.mkdir(cpp_cwd)
		re_compile = False
		with _lock:
			a_out = os.path.join(cpp_cwd, data["app"])
			re_compile = not os.path.isfile(a_out)
			if re_compile:
				with open(a_out, 'w') as fp:
					pass
		if re_compile:
			compile_cmd = get_cp_cmd(app, os.path.join('..',cpp_cwd))
			is_cpp = (compile_cmd[:3] == 'g++')
			ex,stdout,stderr = run_cmd([compile_cmd], {}, 'cpp', 'C/C++编译', 10)
			if ex=='':
				if stdout.strip()!= '' or stderr.strip()!= '':
					ex = stdout if stdout.strip()!='' else stderr
			if ex!='':
				return ex,'',''
		if sys.platform=='linux':
			# cmd = [f'ulimit -t 2; ulimit -s 2048 ; ASAN_OPTIONS=detect_leaks=1 ./{data["app"]}']
			cmd = [f'./{data["app"]}']
		else:
			cmd = [f'./{data["app"]}']
		with open(os.path.join(cpp_cwd, 'data.json'), 'w') as fp:
			json.dump(data, fp, indent=1, ensure_ascii=False)
		ex, stdout, stderr = run_cmd(cmd, {}, cpp_cwd, 'C/C++运行', 10, False)
		if ex=='' and 'ERROR: AddressSanitizer' in stderr:
			ex, stderr = stderr, ''
		if os.path.isfile(f'{cpp_cwd}/data.json'):
			data.clear()
			with open(f'{cpp_cwd}/data.json') as fp:
				data.update(json.load(fp))
		return ex, stdout, stderr
	except AssertionError as ex:
		return str(ex), '', ''

